/*
 * File:   main.c
 * Author: pengy
 *
 * Created on 2021年7月14日, 下午2:09
 */

#include "main.h"

/**
 * @brief try to get unique id in cluster
 * 
 * 
 */
void bc_getid()
{
    add_event(WAIT_GETID, T800);
    FLAG_SET(FLAG_GETID);
    send_buffer_l2[0] = PACKET_START;
    send_buffer_l2[1] = CMD_GETID;
    send_buffer_l2[2] = device_id;
    send(send_buffer_l2, 3, CCS | ACS | DOSEND);
}

/**
 * @brief info id conflict
 * 
 */
void bc_conflict()
{
    send_buffer_l2[0] = PACKET_START;
    send_buffer_l2[1] = CMD_CONF;
    send_buffer_l2[2] = device_id;
    send(send_buffer_l2, 3, CCS | ACS | DOSEND);
}

/**
 * @brief info other do not candidate
 * 
 */
void bs_candidating()
{
    CHB_RST;
    send_buffer_l2[0] = PACKET_START;
    send_buffer_l2[1] = CMD_CANDIDATING;
    send(send_buffer_l2, 2, CCS | ACS | DOSEND);
}
void bc_setting(uint8_t type)
{
    send_buffer_l2[0] = PACKET_START;
    send_buffer_l2[1] = type;
    send_buffer_l2[2] = cluster_time_num;
    send_buffer_l2[3] = tick;
    send(send_buffer_l2, 4, CCS);
    send(&ts, 2, 0);
    send(cluster_times, cluster_time_num << 1, ACS | DOSEND);
}

/**
 * @brief master broadcast cluster setting
 * 
 */
void bs_mhb()
{
    MHB_RST;
    if (running_state == RUNNING)
        bc_setting(CMD_START | CMD_MHB);
    else if (running_state == PAUSE)
        bc_setting(CMD_PAUSE | CMD_MHB);
    else if (running_state == STOPPED)
        bc_setting(CMD_RST | CMD_MHB);
    else
        err_rst();
}

/**
 * @brief turn alarm and led on for tm 
 * 
 * @param tm 
 */
void alarm_led(uint8_t tm)
{
    add_event(WAIT_ALARM, tm);
    LED_ON;
    ALARM_ON;
}

/**
 * @brief enter AT mode
 * 
 */
inline void enter_AT()
{
    cluster_state = POWERON;
    CHB_STOP;
    MHB_STOP;
    CM_STOP;
    SC_STOP;
    EVENT_RST;
    FLAG_RST;
    add_event(WAIT_AT_ON, T40);
    AT_ON;
    LED_ON;
    clear_buffer;
    packet_rc_state = RC_IDLE;
    FLAG_SET(FLAG_AT);
}

/**
 * @brief exit AT mode
 * 
 */
inline void exit_AT()
{
    AT_OFF;
    LED_OFF;
    add_event(WAIT_AT_OFF, T80);
}

/**
 * @brief update display
 * 
 */
void display_update()
{
    static uint8_t scaler;
    blink = (++scaler & 0x1f) > 0x7 ? 0 : 0xff;
    if (scaler & 0x3)
        return;
    switch (local_state)
    {
    case IDLE:
        blinkmask = 0;
        display[0] = segcode_table[ts.b0 % 10];
        display[1] = segcode_table[ts.b0 / 10];
        display[2] = segcode_table[ts.b1 % 10];
        display[3] = segcode_table[ts.b1 / 10];
        return;
    case SEL_SETTING:
        blinkmask = 0b0001;
    case SEL_START:
        display[0] = segcode_table[cur_setting];
        display[1] = SEGNULL;
        display[2] = SEGNULL;
        display[3] = segcode_table[0x5];
        return;
    case SEL_TIMER:
        blinkmask = 0b0001;
        display[0] = segcode_table[local_cur_timer];
        display[1] = SEGNULL;
        display[2] = segcode_table[local_time_num];
        display[3] = segcode_table[cur_setting];
        return;
    case CHANNEL_SETTING:
        blinkmask = 0b0001;
        display[0] = segcode_table[tmp_ch];
        display[1] = SEGNULL;
        display[2] = SEGNULL;
        display[3] = segcode_table[0xc];
        return;
    case TIME_SETTING_M:
        blinkmask = 0b1100;
        break;
    case TIME_SETTING_S:
        blinkmask = 0b0011;
        break;
    default:
        break;
    }
    display[0] = segcode_table[local_times[local_cur_timer].b0 % 10];
    display[1] = segcode_table[local_times[local_cur_timer].b0 / 10];
    display[2] = segcode_table[local_times[local_cur_timer].b1 % 10];
    display[3] = segcode_table[local_times[local_cur_timer].b1 / 10];
}

void key_mode_single()
{
    switch (local_state)
    {
    case IDLE:
        cur_setting = CLUSTER_SETTING;
        LOCAL_RST;
        local_state = SEL_SETTING;
        break;
    case SEL_SETTING:
        LOCAL_STOP;
        enter_AT();
        local_state = CHANNEL_SETTING;
        break;
    case SEL_TIMER:
        LOCAL_STOP;
        local_state = IDLE;
        break;
    case CHANNEL_SETTING:
        exit_AT();
        local_state = IDLE;
        break;
    case TIME_SETTING_M:
        LOCAL_RST;
        local_state = TIME_SETTING_S;
        break;
    case TIME_SETTING_S:
        LOCAL_RST;
        local_state = TIME_SETTING_M;
        break;
    case SEL_START:
        local_state = IDLE;
        break;
    default:
        break;
    }
}

void key_up_single()
{
    switch (local_state)
    {
    case SEL_START:
        cur_setting = cur_setting < LOCAL_SETTING ? cur_setting + 1 : cur_setting;
        break;
    case SEL_SETTING:
        cur_setting = cur_setting < CUR_SETTING_END ? cur_setting + 1 : cur_setting;
        break;
    case CHANNEL_SETTING:
        tmp_ch = tmp_ch < 9 ? tmp_ch + 1 : tmp_ch;
        break;
    case SEL_TIMER:
        local_cur_timer = local_cur_timer < TIME_NUM - 1 ? local_cur_timer + 1 : local_cur_timer;
        break;
    case TIME_SETTING_M:
        local_times[local_cur_timer].b1 = local_times[local_cur_timer].b1 < 99 ? local_times[local_cur_timer].b1 + 1 : local_times[local_cur_timer].b1;
        break;
    case TIME_SETTING_S:
        tbit_inc(local_times + local_cur_timer, 99, 59);
        break;
    default:
        break;
    }
    LOCAL_RST;
}

void key_down_single()
{
    switch (local_state)
    {
    case SEL_START:
    case SEL_SETTING:
        cur_setting = cur_setting > CUR_SETTING_BEGIN ? cur_setting - 1 : cur_setting;
        break;
    case CHANNEL_SETTING:
        tmp_ch = tmp_ch > 0 ? tmp_ch - 1 : tmp_ch;
        break;
    case SEL_TIMER:
        local_cur_timer = local_cur_timer > 0 ? local_cur_timer - 1 : local_cur_timer;
        break;
    case TIME_SETTING_M:
        local_times[local_cur_timer].b1 = local_times[local_cur_timer].b1 > 0 ? local_times[local_cur_timer].b1 - 1 : local_times[local_cur_timer].b1;
        break;
    case TIME_SETTING_S:
        tbit_dec(local_times + local_cur_timer, 59, 0);
        break;
    default:
        break;
    }
    LOCAL_RST;
}

void key_ok_single()
{
    switch (local_state)
    {
    case SEL_START:
        switch (cur_setting)
        {
        case LOCAL_SETTING:
            timecpy(cluster_times, local_times, local_time_num << 1);
            cluster_time_num = local_time_num;
            cluster_ready = 1;
        case CLUSTER_SETTING:
            ts = cluster_times[0];
            flush_cur_timer();
            bc_setting(CMD_START);
            running_state = RUNNING;
            local_state = IDLE;
            break;

        default:
            break;
        }
        break;
    case IDLE:
        switch (running_state)
        {
        case STOPPED:
            cur_setting = cluster_ready ? CLUSTER_SETTING : LOCAL_SETTING;
            local_state = SEL_START;
            break;
        case RUNNING:
            running_state = PAUSE;
            bc_setting(CMD_PAUSE);
            break;
        case PAUSE:
            if (ts.b0 == 0 && ts.b1 == 0)
            {
                bc_setting(CMD_RST);
                ts = cluster_times[0];
                running_state = STOPPED;
            }
            else
            {
                running_state = RUNNING;
                bc_setting(CMD_START);
            }
            break;

        default:
            err_rst();
        }
        break;
    case SEL_SETTING:
        LOCAL_RST;
        switch (cur_setting)
        {
        case CLUSTER_SETTING:
            for (i = 0; i < TIME_NUM + 2; ++i)
                local_times[i] = cluster_times[i];
            local_time_num = cluster_time_num;
            break;
        case NEW_SETTING:
            for (i = 0; i < TIME_NUM + 2; ++i)
                local_times[i].b0 = 0,
                local_times[i].b1 = 0;
            local_time_num = 0;
            break;
        case LOCAL_SETTING:
            break;
        default:
            err_rst();
        }
        local_cur_timer = 0;
        local_state = SEL_TIMER;
        break;
    case SEL_TIMER:
        LOCAL_RST;
        local_state = TIME_SETTING_M;
        break;
    case CHANNEL_SETTING:
        send("AT+C0", 5, 0);
        send_buffer_l2[0] = tmp_ch + '0';
        send_buffer_l2[1] = '1';
        send(send_buffer_l2, 2, DOSEND);
        FLAG_SET(FLAG_WAIT_SEND);
        local_state = IDLE;
        break;
    case TIME_SETTING_M:
    case TIME_SETTING_S:
        LOCAL_RST;
        local_cur_timer = 0;
        norm_times();
        local_state = SEL_TIMER;
        break;
    default:
        err_rst();
    }
}

void key_mode_long()
{
    switch (local_state)
    {
    case SEL_SETTING:
    case SEL_TIMER:
    case CHANNEL_SETTING:
    case TIME_SETTING_M:
    case TIME_SETTING_S:
        LOCAL_STOP;
        local_state = IDLE;
        break;

    default:
        reset();
        break;
    }
}

void key_ok_long()
{
    bc_setting(CMD_PAUSE);
    bc_setting(CMD_RST);
    ts = cluster_times[0];
    running_state = STOPPED;
}

key_handler single_handlers[] = {key_ok_single, key_down_single, key_up_single, key_mode_single};
key_handler double_handlers[] = {nop, key_down_single, key_up_single, nop}; // TODO: double click to save setting
key_handler long_handlers[] = {key_ok_long, key_down_single, key_up_single, key_mode_long};

const uint8_t click_delta = 20, long_press_low = 85, long_press_high = 100, val_limit = 200;

void _check_key(uint8_t key, uint8_t idx)
{
    static uint8_t key_up[4] = {200, 200, 200, 200}, key_down[4];
    uint8_t next_key_up = key == 1 && key_up[idx] < val_limit ? key_up[idx] + 1 : key_up[idx];
    uint8_t next_key_down = key == 0 && key_down[idx] < val_limit ? key_down[idx] + 1 : key_down[idx];
    if (next_key_down == 2)
    {
        if (next_key_up < click_delta)
        {
            key_up[idx] = 0;
            key_down[idx] = long_press_high + 10;
            double_handlers[idx]();
        }
        else
        {
            key_up[idx] = 0;
            key_down[idx] = next_key_down;
        }
    }
    else if (next_key_down == long_press_high)
    {
        key_down[idx] = long_press_low;
        key_up[idx] = next_key_up;
        long_handlers[idx]();
    }
    else if (next_key_up == 2)
    {
        if (next_key_down >= long_press_low)
            key_up[idx] = 50;
        else
            key_up[idx] = next_key_up;
        key_down[idx] = 0;
    }
    else if (next_key_up == click_delta)
    {
        key_up[idx] = next_key_up;
        key_down[idx] = next_key_down;
        single_handlers[idx]();
    }
    else
    {
        key_up[idx] = next_key_up;
        key_down[idx] = next_key_down;
    }
}

inline void check_key()
{
    static uint8_t check_key_scale;
    if ((++check_key_scale) & 1)
    {
        _check_key(PORTCbits.RC4, 0);
        _check_key(PORTCbits.RC5, 1);
        _check_key(PORTCbits.RC6, 2);
        _check_key(PORTCbits.RC7, 3);
    }
}

void __interrupt() isr(void)
{
    static uint8_t scaler = 0;
    if (TMR4IF)
    {
        TMR4IF = 0;
        if (running_state == RUNNING)
        {
            tick += tick_delta;
            if (tick == 0)
            {
                tbit_dec(&ts, 59, 0);
                flush_cur_timer();
                if (ts.b1 == 0 && ts.b0 == 0)
                {
                    alarm_led(T800);
                    cluster_cur_timer = 0;
                    running_state = PAUSE;
                }
                else if (ts.b1 == cluster_times[cluster_cur_timer].b1 && ts.b0 == cluster_times[cluster_cur_timer].b0)
                {
                    alarm_led(T400);
                    ++cluster_cur_timer;
                }
            }
        }
        ++scaler;
        // Timers
        packet_timeout = packet_timeout > 0 ? packet_timeout - 1 : packet_timeout;
        sc_timeout = sc_timeout > 0 ? sc_timeout - 1 : sc_timeout;
        chb_timeout = chb_timeout > 0 ? chb_timeout - 1 : chb_timeout;
        cm_timeout = cm_timeout > 0 ? cm_timeout - 1 : cm_timeout;
        mhb_timeout = mhb_timeout > 0 ? mhb_timeout - 1 : mhb_timeout;
        local_rst_timeout = (local_rst_timeout > 0 && ((scaler & 0x3) == 0)) ? local_rst_timeout - 1 : local_rst_timeout;
        if (packet_timeout == 1)
            packet_rc_state = RC_IDLE;
        if (sc_timeout == 1)
        {
            cluster_state = CANDIDATE;
            bs_candidating();
            CM_RST;
        }
        if (chb_timeout == 1)
            bs_candidating();
        if (cm_timeout == 1)
        {
            cluster_state = MASTER;
            CHB_STOP;
            bs_mhb();
        }
        if (mhb_timeout == 1)
            bs_mhb();
        if (local_rst_timeout == 1)
            local_state = IDLE;
        display_update();
    }
    // Display update
    if (TMR2IF)
    {
        TMR2IF = 0;
        LATA = segcode;
        LATC = bitcode;
        bitpos = (bitpos + 1) & 0x3;
        segcode = display[bitpos];
        bitcode = (~(uint8_t)(1u << bitpos)) | (blink & blinkmask);
        check_key();
    }
    // Event handler
    if (TMR6IF)
    {
        TMR6IF = 0;
        if (queue_len == 0)
            return;
        --event_delay[cur_event];
        while (queue_len && event_delay[cur_event] == 0)
        {
            switch (event_type[cur_event])
            {
            case WAIT_AT_ON:
                send("AT+RC", 5, DOSEND);
                break;
            case WAIT_AT_OFF:
                FLAG_CLEAR(FLAG_AT);
                if (local_state == IDLE)
                    reset();
                break;
            case WAIT_GETID:
                FLAG_CLEAR(FLAG_GETID);
                cluster_state = SLAVE;
                SC_RST;
                break;
            case WAIT_ALARM:
                ALARM_OFF;
                LED_OFF;
                break;
            default:
                break;
            }
            cur_event = (cur_event + 1) & QUEUE_MASK;
            --queue_len;
        }
    }
    // Communication
    if (TXIF)
    {
        if (send_head != send_tail)
            TXREG = send_buffer[(send_head++) & BUF_MASK];
        else if (TX1STAbits.TRMT == 1)
        {
            TX1STAbits.TXEN = 0;
            if (flags & FLAG_WAIT_SEND)
            {
                exit_AT();
                FLAG_CLEAR(FLAG_WAIT_SEND);
            }
        }
    }
    if (RCIF)
    {
        RC_DOG;
        uint8_t tmp = RCREG;
        rnd(); // Random
        if (flags & FLAG_AT)
        {
            if (packet_rc_state == RC_IDLE && tmp == 'O')
            {
                packet_rc_cnt = 0;
                packet_rc_state = RC_AT;
            }
            else if (packet_rc_state == RC_AT)
            {
                if ((packet_rc_cnt < 6 || (packet_rc_cnt > 6 && packet_rc_cnt < 10)) && rc_ch_pat[packet_rc_cnt] != tmp)
                    goto AT_ERROR;
                else if (packet_rc_cnt == 6)
                {
                    if (tmp < '0' || tmp > '9')
                        goto AT_ERROR;
                    else
                        tmp_ch = tmp - '0';
                }
                if (packet_rc_cnt >= 10)
                    goto RC_RESET;
            }
            else
                goto RC_RESET;
        }
        else
        {
            rc_cs ^= tmp;
            if (packet_rc_state == RC_IDLE && tmp == PACKET_START)
            {
                packet_rc_state = RC_CMD;
                packet_rc_cnt = 0;
                rc_cs = tmp;
            }
            else if (packet_rc_state == RC_CMD && packet_rc_cnt == 1)
            {
                cmd0 = tmp & CMD_MASK;
                if (cmd0 == CMD_CANDIDATING && cluster_state != POWERON && cluster_state != MASTER)
                    packet_rc_state = CMD_CANDIDATING;
                else if (cmd0 == CMD_GETID)
                    packet_rc_state = CMD_GETID;
                else if (cmd0 == CMD_CONF && (flags & FLAG_GETID))
                    packet_rc_state = CMD_CONF;
                else if (cluster_state != POWERON)
                {
                    if (cmd0 == CMD_START)
                        packet_rc_state = running_state != RUNNING ? CMD_START : RC_SYNC;
                    else if (cmd0 == CMD_PAUSE && running_state == RUNNING)
                        packet_rc_state = CMD_PAUSE;
                    else if (cmd0 == CMD_RST && running_state != STOPPED)
                        packet_rc_state = CMD_RST;
                }

                if ((tmp & CMD_MHB) != 0 && cluster_state != POWERON)
                {
                    //! WARNING: ignore checksum
                    if (cluster_state == CANDIDATE)
                    {
                        cluster_state = SLAVE;
                        CHB_STOP;
                        CM_STOP;
                        SC_RST;
                    }
                    else if (cluster_state == SLAVE)
                        SC_RST;
                    else if (cluster_state == MASTER)
                        err_rst();
                }
                else if (packet_rc_state == RC_CMD)
                    goto RC_RESET;
            }
            else if (packet_rc_state == CMD_GETID)
            {
                if (packet_rc_cnt == 2)
                    peer_id = tmp;
                else if (packet_rc_cnt == 3 && rc_cs == 0)
                {
                    if (peer_id == device_id)
                        bc_conflict();
                    goto RC_RESET;
                }
                else
                    goto RC_RESET;
            }
            else if (packet_rc_state == CMD_CONF)
            {
                if (packet_rc_cnt == 2)
                    peer_id = tmp;
                else if (packet_rc_cnt == 3 && rc_cs == 0)
                {
                    if (peer_id == device_id)
                    {
                        device_id = genid();
                        del_event(WAIT_GETID);
                        bc_getid();
                    }
                    goto RC_RESET;
                }
                else
                    goto RC_RESET;
            }
            else if (packet_rc_state == CMD_CANDIDATING)
            {
                if (packet_rc_cnt == 2 && rc_cs == 0)
                {
                    if (cluster_state == CANDIDATE)
                    {
                        cluster_state = SLAVE;
                        CHB_STOP;
                        CM_STOP;
                        SC_RST;
                    }
                    else if (cluster_state == SLAVE)
                        SC_RST;
                    goto RC_RESET;
                }
                else
                    goto RC_RESET;
            }
            else if (packet_rc_state == CMD_START || packet_rc_state == CMD_PAUSE || packet_rc_state == CMD_RST)
            {
                if (packet_rc_cnt == 2)
                {
                    rc_buffer[0] = tmp;
                    rc_target = 6 + (tmp << 1);
                }
                else if (packet_rc_cnt >= 3 && packet_rc_cnt < rc_target)
                    rc_buffer[packet_rc_cnt - 2] = tmp;
                else if (packet_rc_cnt == rc_target && rc_cs == 0)
                {
                    cluster_time_num = rc_buffer[0];
                    for (i = 0; i < (cluster_time_num << 1); ++i)
                        i[(uint8_t *)cluster_times] = rc_buffer[i + 4];
                    for (; i < (TIME_NUM << 1); ++i)
                        i[(uint8_t *)cluster_times] = 0;
                    tick = rc_buffer[1];
                    ts = *((tbit_t *)(rc_buffer + 2));
                    cluster_ready = 1;
                    if (packet_rc_state == CMD_START)
                        running_state = RUNNING;
                    else if (packet_rc_state == CMD_PAUSE)
                        running_state = PAUSE;
                    else if (packet_rc_state == CMD_RST)
                    {
                        ts = cluster_times[0];
                        running_state = STOPPED;
                    }
                    goto RC_RESET;
                }
                else
                    goto RC_RESET;
            }
            else if (packet_rc_state == RC_SYNC && tick > 64 && tick < 192)
            {
                if (packet_rc_cnt == 2)
                {
                    rc_buffer[0] = tmp;
                    rc_target = 6 + (tmp << 1);
                }
                else if (packet_rc_cnt >= 3 && packet_rc_cnt < rc_target)
                    rc_buffer[packet_rc_cnt - 2] = tmp;
                else if (packet_rc_cnt == rc_target && rc_cs == 0)
                {
                    tick = rc_buffer[1];
                    ts = *((tbit_t *)(rc_buffer + 2));
                    goto RC_RESET;
                }
                else
                    goto RC_RESET;
            }
            else
                goto RC_RESET;
        }
        ++packet_rc_cnt;
    }
    return;
AT_ERROR:
    // LED_ON; //TODO: remove after debug
    clear_buffer;
    send("AT+RC", 5, DOSEND);
RC_RESET:
    packet_rc_state = RC_IDLE;
    return;
}

void main(void)
{
    TRISA = 0;
    TRISB = 0b11000101;
    TRISC = 0b11110000;
    ANSELA = 0;
    ANSELB = 0b11000000;
    ANSELC = 0;
    WPUC = 0b11110000;
    ALARM_OFF;
    AT_OFF;
    LED_ON;
    while ((PORTC & 0xf0) != 0xf0)
        ;
    LED_OFF;

    T0CON0 = 0b10010000;
    T0CON1 = 0b01010000;

    T2CLKCON = 0b00000001;
    PR2 = 0xf9;
    T2CON = 0b11110000; //4ms
    TMR2IE = 1;

    T4CLKCON = 0b00000001;
    PR4 = 0xf5;
    T4CON = 0b11110011; //15.68ms
    TMR4IE = 1;

    T6CLKCON = 0b00000001;
    PR6 = 0x9e; // 40.192ms/2
    T6CON = 0b11101001;
    TMR6IE = 1;

    init_uart();

    INTCON = 0b11000000;

    // Init random seed
    AT_ON;
    __delay_ms(40);
    send("AT", 2, DOSEND);
    __delay_ms(40);
    AT_OFF;
    __delay_ms(80);

    device_id = genid();
    bc_getid(); // Try to get unique ID
    for (;;)
        ;
}
